public SelectSingleNode ( string xpath ) : |
||
xpath | string | |
return |
/// <summary> /// Some book layouts rely on the first page facing the second page. A Wall Calendar is one example. /// Here we check if the first content page has this requirement and, if so, we insert a blank "flyleaf" /// page. /// </summary> private void InjectFlyleafIfNeeded(Layout layout) { // the "inside" here means "not counting the cover" var numberOfFrontMatterPagesInside = XMatterDom.SafeSelectNodes("//div[contains(@class,'bloom-frontMatter')]").Count - 1; var firstPageWouldNotBePartOfASpread = numberOfFrontMatterPagesInside % 2 != 0; if (firstPageWouldNotBePartOfASpread) { var lastFrontMatterPage = _bookDom.SelectSingleNode("//div[contains(@class,'bloom-frontMatter')][last()]"); var firstContentPageAndAlsoStartsSpread = _bookDom.SelectSingleNode( "//div[contains(@class,'bloom-frontMatter')][last()]" // last frontmatter page + "/following-sibling::div[contains(@data-page, 'spread-start')]"); // page after that says it needs to be facing the next page if (firstContentPageAndAlsoStartsSpread != null) { var flyDom = new XmlDocument(); flyDom.LoadXml(@" <div class='bloom-flyleaf bloom-frontMatter bloom-page' data-page='required singleton'> <div class='pageLabel'>Flyleaf</div> <div style='height: 100px; width:100%' data-hint='This page was automatically inserted because the following page is marked as part of a two page spread.'> </div> </div>" ); var flyleaf = _bookDom.RawDom.ImportNode(flyDom.FirstChild, true) as XmlElement; flyleaf.SetAttribute("id", Guid.NewGuid().ToString()); lastFrontMatterPage.ParentNode.InsertAfter(flyleaf, lastFrontMatterPage); SizeAndOrientation.UpdatePageSizeAndOrientationClasses(flyleaf, layout); } } }
public static Layout FromDom(HtmlDom dom, Layout defaultIfMissing) { var firstPage = dom.SelectSingleNode("descendant-or-self::div[contains(@class,'bloom-page')]"); if (firstPage == null) { return(defaultIfMissing); } var layout = new Layout { SizeAndOrientation = defaultIfMissing.SizeAndOrientation, Style = defaultIfMissing.Style }; foreach (var part in firstPage.GetStringAttribute("class").SplitTrimmed(' ')) { if (part.ToLowerInvariant().Contains("portrait") || part.ToLowerInvariant().Contains("landscape")) { layout.SizeAndOrientation = SizeAndOrientation.FromString(part); } if (part.ToLowerInvariant().Contains("layout-style-")) { int startIndex = "layout-style-".Length; layout.Style = part.Substring(startIndex, part.Length - startIndex); //reivew: this might let us suck up a style that is no longer listed in any css } } return(layout); }
// This overload is much more convenient for testing. public string CheckBook(HtmlDom dom, string[] languages, Book book = null) { var meta = dom.SelectSingleNode("//meta[@name='bloom-licensed-content-id' and @content]"); if (meta == null) { return(null); } bool didCheck; var problems = GetProblemLanguages(languages, meta.GetStringAttribute("content"), out didCheck); if (problems.Count() == 0) { return(null); } if (!didCheck) { return(LocalizationManager.GetString("PublishTab.Android.CantGetLicenseInfo", kCannotReachLicenseServerMessage)); } var template = LocalizationManager.GetString("PublishTab.Android.UnlicensedLanguages", kUnlicenseLanguageMessage, "{0} will be a language name or a list of them"); // In real life we always have a book and can get nicer names. I put the fallback in for testing. var langs = string.Join(CultureInfo.CurrentCulture.TextInfo.ListSeparator + " ", problems.Select(x => book == null ? WritingSystem.LookupIsoCode.GetLocalizedLanguageName(x, "") : book.PrettyPrintLanguage(x))); return(string.Format(template, langs)); }
public static Layout FromDom(HtmlDom dom, Layout defaultIfMissing) { var firstPage = dom.SelectSingleNode("descendant-or-self::div[contains(@class,'bloom-page')]"); if (firstPage == null) { return(defaultIfMissing); } var layout = new Layout { SizeAndOrientation = defaultIfMissing.SizeAndOrientation, Style = defaultIfMissing.Style }; return(FromPage(firstPage, layout)); }
private static void CopyItemToFieldsInPages(HtmlDom dom, string key, string valueAttribute = null, string[] languagePreferences = null) { if (languagePreferences == null) { languagePreferences = new[] { "*", "en" } } ; MultiTextBase source = dom.GetBookSetting(key); var target = dom.SelectSingleNode("//*[@data-derived='" + key + "']"); if (target == null) { return; } //just put value into the text of the element if (string.IsNullOrEmpty(valueAttribute)) { //clear out what's there now target.RemoveAttribute("lang"); target.InnerText = ""; var form = source.GetBestAlternative(languagePreferences); if (form != null && !string.IsNullOrWhiteSpace(form.Form)) { HtmlDom.SetElementFromUserStringPreservingLineBreaks(target, form.Form); target.SetAttribute("lang", form.WritingSystemId); //this allows us to set the font to suit the language } } else //Put the value into an attribute. The license image goes through this path. { target.SetAttribute(valueAttribute, source.GetBestAlternativeString(languagePreferences)); if (source.Empty) { //if the license image is empty, make sure we don't have some alternative text //about the image being missing or slow to load target.SetAttribute("alt", ""); //over in javascript land, @alt will get set appropriately when the image url is not empty. } } }
private static void CopyItemToFieldsInPages(HtmlDom dom, string key, string valueAttribute = null, string[] languagePreferences= null) { if (languagePreferences == null) languagePreferences = new[] {"*", "en"}; MultiTextBase source = dom.GetBookSetting(key); var target = dom.SelectSingleNode("//*[@data-derived='" + key + "']"); if (target == null) { return; } //just put value into the text of the element if (string.IsNullOrEmpty(valueAttribute)) { //clear out what's there now target.RemoveAttribute("lang"); target.InnerText = ""; var form = source.GetBestAlternative(languagePreferences); if (form != null && !string.IsNullOrWhiteSpace(form.Form)) { HtmlDom.SetElementFromUserStringPreservingLineBreaks(target, form.Form); target.SetAttribute("lang", form.WritingSystemId); //this allows us to set the font to suit the language } } else //Put the value into an attribute. The license image goes through this path. { target.SetAttribute(valueAttribute, source.GetBestAlternativeString(languagePreferences)); if (source.Empty) { //if the license image is empty, make sure we don't have some alternative text //about the image being missing or slow to load target.SetAttribute("alt", ""); //over in javascript land, @alt will get set appropriately when the image url is not empty. } } }
public void Save_UpdatesMetadataTitle() { _bookDom = new HtmlDom( @"<html> <head> <meta content='text/html; charset=utf-8' http-equiv='content-type' /> <title>Test Shell</title> <link rel='stylesheet' href='Basic Book.css' type='text/css' /> <link rel='stylesheet' href='../../previewMode.css' type='text/css' />; </head> <body> <div class='bloom-page'> <div class='bloom-page' id='guid3'> <textarea lang='en' data-book='bookTitle'>original</textarea> </div> </div> </body></html>"); var book = CreateBook(); var titleElt = _bookDom.SelectSingleNode("//textarea"); titleElt.InnerText = "changed & <mangled>"; book.Save(); Assert.That(_metadata.Title, Is.EqualTo("changed & <mangled>")); }
/// <summary> /// Return the top-level document that should be displayed in the browser for the current page. /// </summary> /// <returns></returns> public HtmlDom GetXmlDocumentForEditScreenWebPage() { var path = FileLocator.GetFileDistributedWithApplication(Path.Combine(BloomFileLocator.BrowserRoot, "bookEdit", "EditViewFrame.html")); // {simulatedPageFileInBookFolder} is placed in the template file where we want the source file for the 'page' iframe. // We don't really make a file for the page, the contents are just saved in our local server. // But we give it a url that makes it seem to be in the book folder so local urls work. // See EnhancedImageServer.MakeSimulatedPageFileInBookFolder() for more details. var frameText = RobustFile.ReadAllText(path, Encoding.UTF8).Replace("{simulatedPageFileInBookFolder}", _currentPage.Key); var dom = new HtmlDom(XmlHtmlConverter.GetXmlDomFromHtml(frameText)); if (_currentlyDisplayedBook.BookInfo.ToolboxIsOpen) { // Make the toolbox initially visible. // What we have to do to accomplish this is pretty non-intutive. It's a consequence of the way // the pure-drawer CSS achieves the open/close effect. This input is a check-box, so clicking it // changes the state of things in a way that all the other CSS can depend on. var toolboxCheckBox = dom.SelectSingleNode("//input[@id='pure-toggle-right']"); if (toolboxCheckBox != null) toolboxCheckBox.SetAttribute("checked", "true"); } return dom; }
public void Save_UpdatesMetadataCreditsRemovingP() { _bookDom = new HtmlDom( @"<html> <head> <meta content='text/html; charset=utf-8' http-equiv='content-type' /> <title>Test Shell</title> <link rel='stylesheet' href='Basic Book.css' type='text/css' /> <link rel='stylesheet' href='../../previewMode.css' type='text/css' />; </head> <body> <div class='bloom-page'> <div class='bloom-page' id='guid3'> <textarea lang='en' data-book='originalAcknowledgments'><p>original</p></textarea> </div> </div> </body></html>"); var book = CreateBook(); var acksElt = _bookDom.SelectSingleNode("//textarea"); #if __MonoCS__ // may not be needed for Mono 4.x acksElt.OwnerDocument.PreserveWhitespace = true; // Does not preserve newlines on Linux without this #endif acksElt.InnerXml = "<p>changed</p>" + Environment.NewLine + "<p>more changes</p>"; book.Save(); Assert.That(_metadata.Credits, Is.EqualTo("changed" + Environment.NewLine + "more changes")); }
public void Save_UpdatesMetadataCreditsRemovingBreaks() { _bookDom = new HtmlDom( @"<html> <head> <meta content='text/html; charset=utf-8' http-equiv='content-type' /> <title>Test Shell</title> <link rel='stylesheet' href='Basic Book.css' type='text/css' /> <link rel='stylesheet' href='../../previewMode.css' type='text/css' />; </head> <body> <div class='bloom-page'> <div class='bloom-page' id='guid3'> <textarea lang='en' data-book='originalAcknowledgments'>original</textarea> </div> </div> </body></html>"); var book = CreateBook(); var acksElt = _bookDom.SelectSingleNode("//textarea"); acksElt.InnerXml = "changed" + Environment.NewLine + "<br />more changes"; book.Save(); Assert.That(_metadata.Credits, Is.EqualTo("changed" + Environment.NewLine + "more changes")); }
/// <summary> /// Earlier, we handed out a single-page version of the document. Now it has been edited, /// so we now we need to fold changes back in /// </summary> public void SavePage(HtmlDom editedPageDom) { Debug.Assert(IsEditable); try { // This is needed if the user did some ChangeLayout (origami) manipulation. This will populate new // translationGroups with .bloom-editables and set the proper classes on those editables to match the current multilingual settings. UpdateEditableAreasOfElement(editedPageDom); //replace the corresponding page contents in our DOM with what is in this PageDom XmlElement pageFromEditedDom = editedPageDom.SelectSingleNodeHonoringDefaultNS("//div[contains(@class, 'bloom-page')]"); string pageId = pageFromEditedDom.GetAttribute("id"); var pageFromStorage = GetPageFromStorage(pageId); HtmlDom.ProcessPageAfterEditing(pageFromStorage, pageFromEditedDom); _bookData.SuckInDataFromEditedDom(editedPageDom); //this will do an updatetitle // When the user edits the styles on a page, the new or modified rules show up in a <style/> element with title "userModifiedStyles". Here we copy that over to the book DOM. var userModifiedStyles = editedPageDom.SelectSingleNode("html/head/style[@title='userModifiedStyles']"); if (userModifiedStyles != null) { GetOrCreateUserModifiedStyleElementFromStorage().InnerXml = userModifiedStyles.InnerXml; //Debug.WriteLine("Incoming User Modified Styles: " + userModifiedStyles.OuterXml); } Save(); _storage.UpdateBookFileAndFolderName(_collectionSettings); //review used to have UpdateBookFolderAndFileNames(data); //Enhance: if this is only used to re-show the thumbnail, why not limit it to if this is the cover page? //e.g., look for the class "cover" InvokeContentsChanged(null); //enhance: above we could detect if anything actually changed } catch (Exception error) { var msg = LocalizationManager.GetString("Errors.CouldNotSavePage", "Bloom had trouble saving a page. Please click Details below and report this to us. Then quit Bloom, run it again, and check to see if the page you just edited is missing anything. Sorry!"); ErrorReport.NotifyUserOfProblem(error, msg); } }
public static Layout FromDom(HtmlDom dom, Layout defaultIfMissing) { var firstPage = dom.SelectSingleNode("descendant-or-self::div[contains(@class,'bloom-page')]"); if (firstPage == null) return defaultIfMissing; var layout = new Layout {SizeAndOrientation = defaultIfMissing.SizeAndOrientation, Style= defaultIfMissing.Style}; foreach (var part in firstPage.GetStringAttribute("class").SplitTrimmed(' ')) { if (part.ToLower().Contains("portrait") || part.ToLower().Contains("landscape")) { layout.SizeAndOrientation = SizeAndOrientation.FromString(part); } if (part.ToLower().Contains("layout-style-")) { int startIndex = "layout-style-".Length; layout.Style = part.Substring(startIndex, part.Length-startIndex); //reivew: this might let us suck up a style that is no longer listed in any css } } return layout; }
/// <summary> /// Earlier, we handed out a single-page version of the document. Now it has been edited, /// so we now we need to fold changes back in /// </summary> public void SavePage(HtmlDom editedPageDom) { Debug.Assert(IsEditable); try { //replace the corresponding page contents in our DOM with what is in this PageDom XmlElement divElement = editedPageDom.SelectSingleNodeHonoringDefaultNS("//div[contains(@class, 'bloom-page')]"); string pageDivId = divElement.GetAttribute("id"); var page = GetPageFromStorage(pageDivId); /* * there are too many non-semantic variations that are introduced by various processes (e.g. self closing of empy divs, handling of non-ascii) * var selfClosingVersion = divElement.InnerXml.Replace("\"></div>", "\"/>"); if (page.InnerXml == selfClosingVersion) { return; } */ page.InnerXml = divElement.InnerXml; _bookData.SuckInDataFromEditedDom(editedPageDom);//this will do an updatetitle // When the user edits the styles on a page, the new or modified rules show up in a <style/> element with id "customStyles". Here we copy that over to the book DOM. var customStyles = editedPageDom.SelectSingleNode("html/head/style[@id='customStyles']"); if (customStyles != null) { GetOrCreateCustomStyleElementFromStorage().InnerXml = customStyles.InnerXml; Debug.WriteLine("Incoming CustomStyles: " + customStyles.OuterXml); } //Debug.WriteLine("CustomBookStyles: " + GetOrCreateCustomStyleElementFromStorage().OuterXml); try { _storage.Save(); } catch (Exception error) { ErrorReport.NotifyUserOfProblem(error, "There was a problem saving"); } _storage.UpdateBookFileAndFolderName(_collectionSettings); //review used to have UpdateBookFolderAndFileNames(data); //Enhance: if this is only used to re-show the thumbnail, why not limit it to if this is the cover page? //e.g., look for the class "cover" InvokeContentsChanged(null); //enhance: above we could detect if anything actually changed } catch (Exception error) { Palaso.Reporting.ErrorReport.NotifyUserOfProblem(error, "Bloom had trouble saving a page. Please click Details below and report this to us. Then quit Bloom, run it again, and check to see if the page you just edited is missing anything. Sorry!"); } }
private void ThumbnailReady(string exportFolder, HtmlDom dom, Image image) { string term; string week; try { term = dom.SelectSingleNode("//div[contains(@data-book,'term')]").InnerText.Trim(); week = dom.SelectSingleNode("//div[contains(@data-book,'week')]").InnerText.Trim(); } catch (Exception e) { Debug.Fail("Book missing either term or week variable"); throw new ApplicationException("This page is lacking either a term or week data-book variable."); } //the selector for day one is different because it doesn't have @data-* attribute XmlElement dayNode = dom.SelectSingleNode("//div[contains(@class,'DayStyle')]"); string page="?"; // many pupil books don't have a specific day per page if (dom.SelectSingleNode("//div[contains(@class,'day5Left')]") != null) // in P2, we have 2 pages for day 5, so we can't use the 'DayStyle' to differentiate them { page = "5"; } else if (dom.SelectSingleNode("//div[contains(@class,'day5Right')]") != null) { page = "6"; } else if (dayNode != null) { page = dayNode.InnerText.Trim(); } else { if (dom.SelectSingleNode("//div[contains(@class,'page1') or contains(@class,'storyPageLeft')]") != null) { page = "1"; } else if (dom.SelectSingleNode("//div[contains(@class,'page2') or contains(@class,'storyPageRight')]") != null) { page = "2"; } else if (dom.SelectSingleNode("//div[contains(@class,'page3') or contains(@class,'thirdPage')]") != null) { page = "3"; } else if (dom.SelectSingleNode("//div[contains(@class,'page4') or contains(@class,'fourthPage')]") != null) { page = "4"; } else { Debug.Fail("Couldn't figure out what page this is."); } } var fileName = Language1Iso639Code + "-t" + term + "-w" + week + "-p" + page + ".png"; //just doing image.Save() works for .bmp and .jpg, but not .png using (var b = new Bitmap(image)) { SIL.IO.RobustIO.SaveImage(b, Path.Combine(exportFolder, fileName)); } }
private void ThumbnailReady(string exportFolder, HtmlDom dom, Image image) { var term = dom.SelectSingleNode("//div[contains(@data-book,'term')]").InnerText.Trim(); var week = dom.SelectSingleNode("//div[contains(@data-book,'week')]").InnerText.Trim(); //the selector for day one is different because it doesn't have @data-* attribute var day = dom.SelectSingleNode("//div[contains(@class,'DayStyle')]").InnerText.Trim(); var fileName = Language1Iso639Code + "-t" + term + "-w" + week + "-d" + day + ".png"; //just doing image.Save() works for .bmp and .jpg, but not .png using (var b = new Bitmap(image)) { b.Save(Path.Combine(exportFolder, fileName)); } }
public void Save_UpdatesMetadataIsbnAndPageCount() { _bookDom = new HtmlDom( @"<html> <head> <meta content='text/html; charset=utf-8' http-equiv='content-type' /> <title>Test Shell</title> <link rel='stylesheet' href='Basic Book.css' type='text/css' /> <link rel='stylesheet' href='../../previewMode.css' type='text/css' />; </head> <body> <div class='bloom-page' id='guid3'> <textarea lang='en' data-book='ISBN'>original</textarea> </div> </body></html>"); var book = CreateBook(); var isbnElt = _bookDom.SelectSingleNode("//textarea"); isbnElt.InnerText = "978-0-306-40615-7"; book.Save(); Assert.That(book.BookInfo.Isbn, Is.EqualTo("978-0-306-40615-7")); var dom = book.GetEditableHtmlDomForPage(book.GetPages().First()); isbnElt = dom.SelectSingleNode("//textarea"); isbnElt.InnerText = " "; book.SavePage(dom); book.Save(); Assert.That(_metadata.Isbn, Is.EqualTo("")); }
public void Save_UpdatesMetadataIsbn() { _bookDom = new HtmlDom( @"<html> <head> <meta content='text/html; charset=utf-8' http-equiv='content-type' /> <title>Test Shell</title> <link rel='stylesheet' href='Basic Book.css' type='text/css' /> <link rel='stylesheet' href='../../previewMode.css' type='text/css' />; </head> <body> <div class='bloom-page'> <div class='bloom-page' id='guid3'> <textarea lang='en' data-book='ISBN'>original</textarea> </div> </div> </body></html>"); var book = CreateBook(); var isbnElt = _bookDom.SelectSingleNode("//textarea"); isbnElt.InnerText = "978-0-306-40615-7"; book.Save(); Assert.That(book.BookInfo.Isbn, Is.EqualTo("978-0-306-40615-7")); // todo: reinstate this when this bug is fixed: https://trello.com/c/CaUlk8kN/546-clearing-isbn-does-not-clear-data-div. //isbnElt.InnerText = " "; //book.Save(); //Assert.That(_metadata.volumeInfo.industryIdentifiers.Length, Is.EqualTo(0)); }