public static string GetHtmlForEntry(XmlNode entry) { var b = new StringBuilder(); b.AppendLine("<div class='entry'><table>"); var lexicalUnitNode = entry.SelectSingleNode("lexical-unit"); if (lexicalUnitNode != null) { AddMultiTextHtml(b, "lexeme form", lexicalUnitNode); } foreach (XmlNode node in entry.SafeSelectNodes("citation")) { AddMultiTextHtml(b, "citation form", node); } foreach (XmlNode field in entry.SafeSelectNodes("relation")) { var type = field.GetStringAttribute("type"); var id = field.GetStringAttribute("ref"); var formNode = GetFormNodeForReferencedEntry(entry.OwnerDocument, id); if (null==formNode) { b.AppendFormat("Could not locate {0}", id); continue; } AddMultiTextHtml(b, type, formNode); } foreach (XmlNode field in entry.SafeSelectNodes("field")) { var label = field.GetStringAttribute("type"); AddMultiTextHtml(b, label, field); } foreach (XmlNode note in entry.SafeSelectNodes("note")) { AddMultiTextHtml(b, "note", note); } foreach (XmlNode node in entry.SafeSelectNodes("sense")) { AddSense(b, 0, node); } b.AppendLine("</table></div>"); return b.ToString(); }
private static void DoPostMerge(string outputPath, XmlNode mergedNode) { foreach (XmlNode partNode in mergedNode.SafeSelectNodes("layout/generate")) { partNode.Attributes.Remove(partNode.Attributes["combinedkey"]); } using (var writer = XmlWriter.Create(outputPath, CanonicalXmlSettings.CreateXmlWriterSettings())) { writer.WriteNode(mergedNode.CreateNavigator(), true); } }
/// <summary> /// Normally, the connection between bloom-translationGroups and the dataDiv is that each bloom-editable child /// (which has an @lang) pulls the corresponding string from the dataDiv. This happens in BookData. /// /// That works except in the case of xmatter which a) start empty and b) only normally get filled with /// .bloom-editable's for the current languages. Then, when bloom would normally show a source bubble listing /// the string in other languages, well there's nothing to show (the bubble can't pull from dataDiv). /// So our solution here is to pre-pack the translationGroup with bloom-editable's for each of the languages /// in the data-div. /// The original (an possibly only) instance of this is with book titles. See bl-1210. /// </summary> public static void PrepareDataBookTranslationGroups(XmlNode pageOrDocumentNode, IEnumerable<string> languageCodes) { //At first, I set out to select all translationGroups that have child .bloomEditables that have data-book attributes //however this has implications on other fields, noticeably the acknowledgments. So in order to get this fixed //and not open another can of worms, I've reduce the scope of this //fix to just the bookTitle, so I'm going with findOnlyBookTitleFields for now var findAllDataBookFields = "descendant-or-self::*[contains(@class,'bloom-translationGroup') and descendant::div[@data-book and contains(@class,'bloom-editable')]]"; var findOnlyBookTitleFields = "descendant-or-self::*[contains(@class,'bloom-translationGroup') and descendant::div[@data-book='bookTitle' and contains(@class,'bloom-editable')]]"; foreach (XmlElement groupElement in pageOrDocumentNode.SafeSelectNodes(findOnlyBookTitleFields)) { foreach (var lang in languageCodes) { MakeElementWithLanguageForOneGroup(groupElement, lang); } } }
public XmlNode GetNodeToMerge(XmlNode nodeToMatch, XmlNode parentToSearchIn, HashSet<XmlNode> acceptableTargets) { if (nodeToMatch == null || parentToSearchIn == null) return null; var nodeName = nodeToMatch.LocalName; var ourForms = nodeToMatch.SafeSelectNodes(nodeName + "/form"); foreach (XmlNode example in parentToSearchIn.SafeSelectNodes(nodeName)) { if (!acceptableTargets.Contains(example)) continue; XmlNodeList forms = example.SafeSelectNodes("form"); if(!SameForms(example, forms, ourForms)) continue; return example; } return null; //couldn't find a match }
/// <summary> /// We stick 'contentLanguage2' and 'contentLanguage3' classes on editable things in bilingual and trilingual books /// </summary> public static void UpdateContentLanguageClasses(XmlNode elementOrDom, string vernacularIso, string national1Iso, string national2Iso, string contentLanguageIso2, string contentLanguageIso3) { var multilingualClass = "bloom-monolingual"; var contentLanguages = new Dictionary<string, string>(); contentLanguages.Add(vernacularIso, "bloom-content1"); if (!String.IsNullOrEmpty(contentLanguageIso2) && vernacularIso != contentLanguageIso2) { multilingualClass = "bloom-bilingual"; contentLanguages.Add(contentLanguageIso2, "bloom-content2"); } if (!String.IsNullOrEmpty(contentLanguageIso3) && vernacularIso != contentLanguageIso3 && contentLanguageIso2 != contentLanguageIso3) { multilingualClass = "bloom-trilingual"; Debug.Assert(!String.IsNullOrEmpty(contentLanguageIso2), "shouldn't have a content3 lang with no content2 lang"); contentLanguages.Add(contentLanguageIso3, "bloom-content3"); } //Stick a class in the page div telling the stylesheet how many languages we are displaying (only makes sense for content pages, in Jan 2012). foreach (XmlElement pageDiv in elementOrDom.SafeSelectNodes("descendant-or-self::div[contains(@class,'bloom-page') and not(contains(@class,'bloom-frontMatter')) and not(contains(@class,'bloom-backMatter'))]")) { HtmlDom.RemoveClassesBeginingWith(pageDiv, "bloom-monolingual"); HtmlDom.RemoveClassesBeginingWith(pageDiv, "bloom-bilingual"); HtmlDom.RemoveClassesBeginingWith(pageDiv, "bloom-trilingual"); HtmlDom.AddClassIfMissing(pageDiv, multilingualClass); } foreach (XmlElement group in elementOrDom.SafeSelectNodes(".//*[contains(@class,'bloom-translationGroup')]")) { var isXMatter = @group.SafeSelectNodes("ancestor::div[contains(@class,'bloom-frontMatter') or contains(@class,'bloom-backMatter')]").Count > 0; foreach (XmlElement e in @group.SafeSelectNodes(".//textarea | .//div")) //nb: we don't necessarily care that a div is editable or not { var lang = e.GetAttribute("lang"); HtmlDom.RemoveClassesBeginingWith(e, "bloom-content");//they might have been a given content lang before, but not now if (isXMatter && lang == national1Iso) { HtmlDom.AddClass(e, "bloom-contentNational1"); } if (isXMatter && !String.IsNullOrEmpty(national2Iso) && lang == national2Iso) { HtmlDom.AddClass(e, "bloom-contentNational2"); } foreach (var language in contentLanguages) { if (lang == language.Key) { HtmlDom.AddClass(e, language.Value); break;//don't check the other languages } } } } }
private static void PrepareElementsOnPageOneLanguage(XmlNode pageDiv, string isoCode) { foreach (XmlElement groupElement in pageDiv.SafeSelectNodes("descendant-or-self::*[contains(@class,'bloom-translationGroup')]")) { MakeElementWithLanguageForOneGroup(groupElement, isoCode, "*"); //remove any elements in the translationgroup which don't have a lang (but ignore any label elements, which we're using for annotating groups) foreach (XmlElement elementWithoutLanguage in groupElement.SafeSelectNodes("textarea[not(@lang)] | div[not(@lang) and not(self::label)]")) { elementWithoutLanguage.ParentNode.RemoveChild(elementWithoutLanguage); } } //any editable areas which still don't have a language, set them to the vernacular (this is used for simple templates (non-shell pages)) foreach ( XmlElement element in pageDiv.SafeSelectNodes(//NB: the jscript will take items with bloom-editable and set the contentEdtable to true. "descendant-or-self::textarea[not(@lang)] | descendant-or-self::*[(contains(@class, 'bloom-editable') or @contentEditable='true' or @contenteditable='true') and not(@lang)]") ) { element.SetAttribute("lang", isoCode); } foreach (XmlElement e in pageDiv.SafeSelectNodes("descendant-or-self::*[starts-with(text(),'{')]")) { foreach (var node in e.ChildNodes) { XmlText t = node as XmlText; if (t != null && t.Value.StartsWith("{")) t.Value = ""; //otherwise html tidy will throw away spans (at least) that are empty, so we never get a chance to fill in the values. } } }
// private static void ClearAwayAllTranslations(XmlNode element) // { // // foreach (XmlNode node in element.ChildNodes)//.SafeSelectNodes(String.Format("//*[@lang='{0}']", _collectionSettings.Language1Iso639Code))) // { // if (node.NodeType == XmlNodeType.Text) // { // node.InnerText = String.Empty; // } // else // { // ClearAwayAllTranslations(node); // } // } // //after removing text, we could still be left with the line breaks between them // if (element.ChildNodes != null) // { // var possibleBrNodes = new List<XmlNode>(); // possibleBrNodes.AddRange(from XmlNode x in element.ChildNodes select x); // foreach (XmlNode node in possibleBrNodes) // { // if (node.NodeType == XmlNodeType.Element && node.Name.ToLower() == "br") // { // node.ParentNode.RemoveChild(node); // } // } // } // } /// <summary> /// When building on templates, we usually want to have some sample text, but don't let them bleed through to what the user sees /// </summary> /// <param name="element"></param> private static void ClearAwayDraftText(XmlNode element) { //clear away everything done in language "x" var nodesInLangX = new List<XmlNode>(); nodesInLangX.AddRange(from XmlNode x in element.SafeSelectNodes(String.Format("//*[@lang='x']")) select x); foreach (XmlNode node in nodesInLangX) { node.ParentNode.RemoveChild(node); } }
private static void AddSense(StringBuilder builder, int indentLevel, XmlNode senseNode) { builder.Append("<tr><td><span class='fieldLabel'>Sense</span></td>"); var pos = senseNode.SelectSingleNode("grammatical-info"); if (pos != null) { builder.AppendFormat("<td><span id='pos'> {0}</span></td>" + Environment.NewLine, pos.GetStringAttribute("value")); } builder.Append("</tr>"); foreach (XmlNode def in senseNode.SafeSelectNodes("definition")) { AddMultiTextHtml(builder, "definition", def); } foreach (XmlNode gloss in senseNode.SafeSelectNodes("gloss")) { AddSingleFormHtml(gloss, builder, "gloss"); } foreach (XmlNode example in senseNode.SafeSelectNodes("example")) { AddMultiTextHtml(builder, "example", example); foreach (XmlNode trans in example.SafeSelectNodes("translation")) { AddMultiTextHtml(builder, "translation", trans); } } foreach (XmlNode field in senseNode.SafeSelectNodes("field")) { var label = field.GetStringAttribute("type"); AddMultiTextHtml(builder, label, field); } foreach (XmlNode node in senseNode.SafeSelectNodes("illustration")) { builder.AppendFormat("<tr><td><span class='fieldLabel'>illustration</span></td><td>(an image)</td>"); } foreach (XmlNode trait in senseNode.SafeSelectNodes("trait")) { var label = trait.GetStringAttribute("name"); var traitValue = trait.GetStringAttribute("value"); builder.AppendFormat("<tr><td><span class='fieldLabel'>{0}</span></td><td>{1}</td>", label, traitValue); } foreach (XmlNode note in senseNode.SafeSelectNodes("note")) { AddMultiTextHtml(builder, "note", note); } }
private static void AddMultiTextHtml(StringBuilder b, string label, XmlNode node) { foreach (XmlNode formNode in node.SafeSelectNodes("form")) { AddSingleFormHtml(formNode, b, label); } }
private static void ProcessEntries(XmlWriter writer, IMergeEventListener eventListener, IMergeStrategy mergingStrategy, XmlNode ancestorDom, HashSet<string> processedIds, XmlNode sourceDom, string sourceLabel, string sourcePath, IDictionary<string, XmlNode> otherIdNodeIndex, string otherLabel, string otherPath) { foreach (XmlNode sourceEntry in sourceDom.SafeSelectNodes("lift/entry")) { ProcessEntry(writer, eventListener, mergingStrategy, ancestorDom, processedIds, sourceEntry, sourceLabel, sourcePath, otherIdNodeIndex, otherLabel, otherPath); } }
/// <summary> /// We stick 'contentLanguage2' and 'contentLanguage3' classes on editable things in bilingual and trilingual books /// </summary> public static void UpdateContentLanguageClasses(XmlNode elementOrDom, CollectionSettings settings, string vernacularIso, string contentLanguageIso2, string contentLanguageIso3) { var multilingualClass = "bloom-monolingual"; var contentLanguages = new Dictionary<string, string>(); contentLanguages.Add(vernacularIso, "bloom-content1"); if (!String.IsNullOrEmpty(contentLanguageIso2) && vernacularIso != contentLanguageIso2) { multilingualClass = "bloom-bilingual"; contentLanguages.Add(contentLanguageIso2, "bloom-content2"); } if (!String.IsNullOrEmpty(contentLanguageIso3) && vernacularIso != contentLanguageIso3 && contentLanguageIso2 != contentLanguageIso3) { multilingualClass = "bloom-trilingual"; contentLanguages.Add(contentLanguageIso3, "bloom-content3"); Debug.Assert(!String.IsNullOrEmpty(contentLanguageIso2), "shouldn't have a content3 lang with no content2 lang"); } //Stick a class in the page div telling the stylesheet how many languages we are displaying (only makes sense for content pages, in Jan 2012). foreach ( XmlElement pageDiv in elementOrDom.SafeSelectNodes( "descendant-or-self::div[contains(@class,'bloom-page') and not(contains(@class,'bloom-frontMatter')) and not(contains(@class,'bloom-backMatter'))]") ) { HtmlDom.RemoveClassesBeginingWith(pageDiv, "bloom-monolingual"); HtmlDom.RemoveClassesBeginingWith(pageDiv, "bloom-bilingual"); HtmlDom.RemoveClassesBeginingWith(pageDiv, "bloom-trilingual"); HtmlDom.AddClassIfMissing(pageDiv, multilingualClass); } // This is the "code" part of the visibility system: https://goo.gl/EgnSJo foreach (XmlElement group in elementOrDom.SafeSelectNodes(".//*[contains(@class,'bloom-translationGroup')]")) { var dataDefaultLanguages = HtmlDom.GetAttributeValue(group, "data-default-languages").Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries); //nb: we don't necessarily care that a div is editable or not foreach (XmlElement e in @group.SafeSelectNodes(".//textarea | .//div")) { 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 == settings.Language2Iso639Code) { HtmlDom.AddClass(e, "bloom-contentNational1"); } if (lang == settings.Language3Iso639Code) { HtmlDom.AddClass(e, "bloom-contentNational2"); } HtmlDom.RemoveClassesBeginingWith(e, "bloom-visibility-code"); if (ShouldNormallyShowEditable(lang, dataDefaultLanguages, contentLanguageIso2, contentLanguageIso3, settings)) { HtmlDom.AddClass(e, "bloom-visibility-code-on"); } UpdateRightToLeftSetting(settings, e, lang); } } }
/// <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); } }
public void UpdatePageSplitMode(XmlNode node) { //NB: this can currently only split pages, not move them together. Doable, just not called for by the UI or unit tested yet. if (ElementDistribution == ElementDistributionChoices.CombinedPages) return; var combinedPages = node.SafeSelectNodes("descendant-or-self::div[contains(@class,'bloom-combinedPage')]"); foreach (XmlElement pageDiv in combinedPages) { XmlElement trailer = (XmlElement) pageDiv.CloneNode(true); pageDiv.ParentNode.InsertAfter(trailer, pageDiv); pageDiv.SetAttribute("class", pageDiv.GetAttribute("class").Replace("bloom-combinedPage", "bloom-leadingPage")); var leader = pageDiv; trailer.SetAttribute("class", trailer.GetAttribute("class").Replace("bloom-combinedPage", "bloom-trailingPage")); //give all new ids to both pages leader.SetAttribute("id", Guid.NewGuid().ToString()); trailer.SetAttribute("id", Guid.NewGuid().ToString()); //now split the elements foreach (XmlElement div in leader.SafeSelectNodes("descendant-or-self::*[contains(@class, 'bloom-trailingElement')]")) { div.ParentNode.RemoveChild(div); } foreach (XmlElement div in trailer.SafeSelectNodes("descendant-or-self::*[contains(@class, 'bloom-leadingElement')]")) { div.ParentNode.RemoveChild(div); } } }
// 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); } }
public static void UpdatePageSizeAndOrientationClasses(XmlNode node, Layout layout) { foreach (XmlElement pageDiv in node.SafeSelectNodes("descendant-or-self::div[contains(@class,'bloom-page')]")) { RemoveClassesContaining(pageDiv, "layout-"); RemoveClassesContaining(pageDiv, "Landscape"); RemoveClassesContaining(pageDiv, "Portrait"); foreach (var cssClassName in layout.ClassNames) { AddClass(pageDiv, cssClassName); } } }