diff --git a/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/HtmlDocumentProcessorPlugin.java b/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/HtmlDocumentProcessorPlugin.java index 843150d1..15163c6c 100644 --- a/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/HtmlDocumentProcessorPlugin.java +++ b/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/HtmlDocumentProcessorPlugin.java @@ -7,9 +7,7 @@ import nu.marginalia.converting.processor.MetaRobotsTag; import nu.marginalia.converting.processor.logic.dom.MeasureLengthVisitor; import nu.marginalia.converting.processor.logic.links.FileLinks; import nu.marginalia.converting.processor.logic.links.LinkProcessor; -import nu.marginalia.converting.processor.plugin.specialization.DefaultSpecialization; -import nu.marginalia.converting.processor.plugin.specialization.HtmlProcessorSpecialization; -import nu.marginalia.converting.processor.plugin.specialization.LemmySpecialization; +import nu.marginalia.converting.processor.plugin.specialization.*; import nu.marginalia.language.model.DocumentLanguageData; import nu.marginalia.model.crawl.HtmlFeature; import nu.marginalia.link_parser.LinkParser; @@ -61,8 +59,7 @@ public class HtmlDocumentProcessorPlugin extends AbstractDocumentProcessorPlugin private static final LinkParser linkParser = new LinkParser(); private static final FeedExtractor feedExtractor = new FeedExtractor(linkParser); - private final DefaultSpecialization defaultSpecialization; - private final LemmySpecialization lemmySpecialization; + private final HtmlProcessorSpecializations htmlProcessorSpecializations; @Inject public HtmlDocumentProcessorPlugin( @@ -74,7 +71,9 @@ public class HtmlDocumentProcessorPlugin extends AbstractDocumentProcessorPlugin PubDateSniffer pubDateSniffer, DocumentLengthLogic documentLengthLogic, MetaRobotsTag metaRobotsTag, - DocumentGeneratorExtractor documentGeneratorExtractor, DefaultSpecialization defaultSpecialization, LemmySpecialization lemmySpecialization) { + DocumentGeneratorExtractor documentGeneratorExtractor, + HtmlProcessorSpecializations specializations) + { this.documentLengthLogic = documentLengthLogic; this.minDocumentQuality = minDocumentQuality; this.sentenceExtractor = sentenceExtractor; @@ -86,8 +85,7 @@ public class HtmlDocumentProcessorPlugin extends AbstractDocumentProcessorPlugin this.metaRobotsTag = metaRobotsTag; this.documentGeneratorExtractor = documentGeneratorExtractor; - this.defaultSpecialization = defaultSpecialization; - this.lemmySpecialization = lemmySpecialization; + this.htmlProcessorSpecializations = specializations; } @Override @@ -115,7 +113,7 @@ public class HtmlDocumentProcessorPlugin extends AbstractDocumentProcessorPlugin final var generatorParts = documentGeneratorExtractor.generatorCleaned(doc); - final var specialization = selectSpecialization(generatorParts); + final var specialization = htmlProcessorSpecializations.select(generatorParts); if (!specialization.shouldIndex(url)) { throw new DisqualifiedException(DisqualificationReason.IRRELEVANT); @@ -180,16 +178,6 @@ public class HtmlDocumentProcessorPlugin extends AbstractDocumentProcessorPlugin return new DetailsWithWords(ret, words); } - /** Depending on the generator tag, we may want to use specialized logic for pruning and summarizing the document */ - private HtmlProcessorSpecialization selectSpecialization(DocumentGeneratorExtractor.DocumentGenerator generatorParts) { - - if (generatorParts.keywords().contains("lemmy")) { - return lemmySpecialization; - } - - return defaultSpecialization; - } - private EnumSet documentFlags(Set features, GeneratorType type) { EnumSet flags = EnumSet.noneOf(DocumentFlags.class); diff --git a/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/DefaultSpecialization.java b/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/DefaultSpecialization.java index 8f11daf5..5a441639 100644 --- a/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/DefaultSpecialization.java +++ b/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/DefaultSpecialization.java @@ -11,7 +11,7 @@ import java.util.List; import java.util.Set; @Singleton -public class DefaultSpecialization implements HtmlProcessorSpecialization { +public class DefaultSpecialization implements HtmlProcessorSpecializations.HtmlProcessorSpecializationIf { private final SummaryExtractor summaryExtractor; diff --git a/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/HtmlProcessorSpecialization.java b/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/HtmlProcessorSpecialization.java deleted file mode 100644 index 0dc37e2c..00000000 --- a/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/HtmlProcessorSpecialization.java +++ /dev/null @@ -1,19 +0,0 @@ -package nu.marginalia.converting.processor.plugin.specialization; - -import nu.marginalia.model.EdgeUrl; -import org.jsoup.nodes.Document; - -import java.util.Set; - -/** This interface is used to specify how to process a specific website. - * The implementations of this interface are used by the HtmlProcessor to - * process the HTML documents. - */ -public interface HtmlProcessorSpecialization { - Document prune(Document original); - String getSummary(Document original, - Set importantWords); - - default boolean shouldIndex(EdgeUrl url) { return true; } - default double lengthModifier() { return 1.0; } -} diff --git a/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/HtmlProcessorSpecializations.java b/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/HtmlProcessorSpecializations.java new file mode 100644 index 00000000..a7c7cbba --- /dev/null +++ b/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/HtmlProcessorSpecializations.java @@ -0,0 +1,54 @@ +package nu.marginalia.converting.processor.plugin.specialization; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import nu.marginalia.converting.processor.logic.DocumentGeneratorExtractor; +import nu.marginalia.model.EdgeUrl; +import org.jsoup.nodes.Document; + +import java.util.Set; + +@Singleton +public class HtmlProcessorSpecializations { + private final LemmySpecialization lemmySpecialization; + private final XenForoSpecialization xenforoSpecialization; + private final PhpBBSpecialization phpBBSpecialization; + private final DefaultSpecialization defaultSpecialization; + + @Inject + public HtmlProcessorSpecializations(LemmySpecialization lemmySpecialization, + XenForoSpecialization xenforoSpecialization, + PhpBBSpecialization phpBBSpecialization, DefaultSpecialization defaultSpecialization) { + this.lemmySpecialization = lemmySpecialization; + this.xenforoSpecialization = xenforoSpecialization; + this.phpBBSpecialization = phpBBSpecialization; + this.defaultSpecialization = defaultSpecialization; + } + + /** Depending on the generator tag, we may want to use specialized logic for pruning and summarizing the document */ + public HtmlProcessorSpecializationIf select(DocumentGeneratorExtractor.DocumentGenerator generator) { + if (generator.keywords().contains("lemmy")) { + return lemmySpecialization; + } + if (generator.keywords().contains("xenforo")) { + return xenforoSpecialization; + } + if (generator.keywords().contains("phpbb")) { + return xenforoSpecialization; + } + return defaultSpecialization; + } + + /** This interface is used to specify how to process a specific website. + * The implementations of this interface are used by the HtmlProcessor to + * process the HTML documents. + */ + public interface HtmlProcessorSpecializationIf { + Document prune(Document original); + String getSummary(Document original, + Set importantWords); + + default boolean shouldIndex(EdgeUrl url) { return true; } + default double lengthModifier() { return 1.0; } + } +} diff --git a/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/LemmySpecialization.java b/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/LemmySpecialization.java index 541de587..f85847f4 100644 --- a/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/LemmySpecialization.java +++ b/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/LemmySpecialization.java @@ -2,7 +2,6 @@ package nu.marginalia.converting.processor.plugin.specialization; import com.google.inject.Inject; import com.google.inject.Singleton; -import nu.marginalia.model.EdgeUrl; import nu.marginalia.summary.SummaryExtractor; import org.jsoup.nodes.Document; import org.slf4j.Logger; @@ -12,7 +11,7 @@ import java.util.Set; /** This class is used to specify how to process a website running Lemmy */ @Singleton -public class LemmySpecialization implements HtmlProcessorSpecialization { +public class LemmySpecialization implements HtmlProcessorSpecializations.HtmlProcessorSpecializationIf { private static final Logger logger = LoggerFactory.getLogger(LemmySpecialization.class); private final SummaryExtractor summaryExtractor; diff --git a/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/PhpBBSpecialization.java b/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/PhpBBSpecialization.java new file mode 100644 index 00000000..b92ebcbc --- /dev/null +++ b/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/PhpBBSpecialization.java @@ -0,0 +1,26 @@ +package nu.marginalia.converting.processor.plugin.specialization; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.summary.SummaryExtractor; +import org.jsoup.nodes.Document; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Set; + +@Singleton +public class PhpBBSpecialization extends DefaultSpecialization { + private static final Logger logger = LoggerFactory.getLogger(PhpBBSpecialization.class); + + @Inject + public PhpBBSpecialization(SummaryExtractor summaryExtractor) { + super(summaryExtractor); + } + + @Override + public boolean shouldIndex(EdgeUrl url) { + return url.path.contains("viewtopic.php"); + } +} diff --git a/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/XenForoSpecialization.java b/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/XenForoSpecialization.java new file mode 100644 index 00000000..16a222b3 --- /dev/null +++ b/code/processes/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/specialization/XenForoSpecialization.java @@ -0,0 +1,75 @@ +package nu.marginalia.converting.processor.plugin.specialization; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import nu.marginalia.summary.SummaryExtractor; +import org.jsoup.nodes.Document; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Set; + +@Singleton +public class XenForoSpecialization implements HtmlProcessorSpecializations.HtmlProcessorSpecializationIf { + private static final Logger logger = LoggerFactory.getLogger(XenForoSpecialization.class); + private final SummaryExtractor summaryExtractor; + + @Inject + public XenForoSpecialization(SummaryExtractor summaryExtractor) { + this.summaryExtractor = summaryExtractor; + } + + public Document prune(Document document) { + + // Remove the sidebar + + var newDoc = new Document(document.baseUri()); + var bodyTag = newDoc.appendElement("body"); + var article = bodyTag.appendElement("article"); + var firstTime = document.getElementsByTag("time").first(); + + if (firstTime != null) { + // Ensure we get the publish date + var timeTag = newDoc.createElement("time"); + + timeTag.attr("datetime", firstTime.attr("datetime")); + timeTag.attr("pubdate", "pubdate"); + timeTag.text(firstTime.attr("datetime")); + + article.appendChild(timeTag); + } + + for (var post : document.getElementsByClass("message-inner")) { + String user = post.getElementsByClass("message-name").text(); + String text = post.getElementsByClass("bbWrapper").text(); + article.appendChild(newDoc.createElement("p").text(user + ": " + text)); + } + + return newDoc; + } + + public String getSummary(Document document, Set importantWords) { + StringBuilder summary = new StringBuilder(); + + for (var pTag : document.getElementsByClass("bbWrapper")) { + if (summary.length() > 512) { + break; + } + String text = pTag.text(); + + if (text.isBlank()) + continue; + + summary + .append(text) + .append(' '); + } + + return summaryExtractor.abbreivateSummary(summary.toString()); + } + + @Override + public double lengthModifier() { + return 1.25; + } +} diff --git a/code/processes/converting-process/src/test/java/nu/marginalia/converting/processor/plugin/specialization/XenForoSpecializationTest.java b/code/processes/converting-process/src/test/java/nu/marginalia/converting/processor/plugin/specialization/XenForoSpecializationTest.java new file mode 100644 index 00000000..a10e3ca0 --- /dev/null +++ b/code/processes/converting-process/src/test/java/nu/marginalia/converting/processor/plugin/specialization/XenForoSpecializationTest.java @@ -0,0 +1,48 @@ +package nu.marginalia.converting.processor.plugin.specialization; + +import nu.marginalia.converting.processor.logic.DocumentGeneratorExtractor; +import nu.marginalia.summary.SummaryExtractor; +import nu.marginalia.test.CommonTestData; +import org.jsoup.Jsoup; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +class XenForoSpecializationTest { + + static XenForoSpecialization specialization; + static DocumentGeneratorExtractor generatorExtractor = new DocumentGeneratorExtractor(); + + String thread = CommonTestData.loadTestData("mock-crawl-data/xenforo/thread.html"); + + @BeforeAll + public static void setUpAll() { + specialization = new XenForoSpecialization( + new SummaryExtractor(255, + null, + null, + null, + null, + null)); + } + + @Test + void prune() { + System.out.println(specialization.prune(Jsoup.parse(thread))); + } + + @Test + void generatorExtraction() { + var gen = generatorExtractor.generatorCleaned(Jsoup.parse(thread)); + + System.out.println(gen); + } + + @Test + void getSummary() { + String summary = specialization.getSummary(Jsoup.parse(thread), Set.of("")); + + System.out.println(summary); + } +} \ No newline at end of file diff --git a/code/processes/test-data/src/main/resources/mock-crawl-data/xenforo/thread.html b/code/processes/test-data/src/main/resources/mock-crawl-data/xenforo/thread.html new file mode 100644 index 00000000..dec46e59 --- /dev/null +++ b/code/processes/test-data/src/main/resources/mock-crawl-data/xenforo/thread.html @@ -0,0 +1,2728 @@ + + + + + + + + + + + + my new dell 7010 DT not powering up using win7. | Techist - Tomorrow's Technology Today + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ + + +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +

my new dell 7010 DT not powering up using win7.

+ + + +
+ + + +
+ +
+ + +
+ + +
+ +
+ + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ + + + + +
+ + + +
+ +
+ + + + + + + + + + + + + +
+ + + + +
+ +
+ + +
+
+
+ + S + + +
+
+
+

stevethebrain

+
In Runtime
+ +
+ + + +
+ + + +
+
Messages
+
367
+
+ + + + + + +
+
Location
+
+ + usa + +
+
+ + + + +
+ + + +
+ +
+ + + +
+ +
+ + + + +
+ + + +
+ + + +
+ + + + + + + + + + + + + +
+ + + + + + + + +
+ + + + +
my new dell 7010 DT not powering up using win7.Intel i5, 8gb ddr3 ram, 500GB OS SSD
+ been using my new dell for over a year (mainly to browse web) zero issues except the peskie you may have counterfeit software message which I just X away.
+
+ yesterday I install a new Samsumg EVO870 SSD (I cloned a HDD I had my win7 on + some files). worked great yesterday, I powered down as usale last night.
+
+ today when physicaley powering up w/ power button I observed the power button blicking rapidly. I'm getting 123VAC power to PC just won't boot up.
+
+ now the power button blicks slowely. do I need a new power supply. weird it was working great yesterday w/ new SSD.
+
+ before I start learning to test a power supply an it's related pins, doe's Dells power button blicking rapidly then slowely indicate a failed PSU? thanks for power supply advice
+ +
 
+ + + +
+ + + + + + + + + + + + +
+

Attachments

+
    + + + +
  • + + + + + + + + test PSU1.jpg + + + +
    +
    + test PSU1.jpg +
    + 86.1 KB + + · Views: 0 + +
    +
    +
    +
  • + + + +
+
+ + + +
+ + + + + + +
+ + Last edited: + +
+ + + + + + + + + + + +
+ + + +
+ + +
+ +
+ +
+
+ + +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ +
+ + +
+
+
+ + S + + +
+
+
+

stevethebrain

+
In Runtime
+ +
+ + + +
+ + + +
+
Messages
+
367
+
+ + + + + + +
+
Location
+
+ + usa + +
+
+ + + + +
+ + + +
+ +
+ + + +
+ +
+ + + + +
+ + + +
+ + + +
+ + + + + + + + + + + + + +
+ + + +
+ + + + +
After watching a bunch of dell PSU failures/swaps I bumped into the organe flashing light a builtin problem w/ dells.
+
+ Also learned about the green light on back of PSU.
+
+
+
+ Weird that the resolution was to remove replace each 4GB RAM module, iniailly didn’t think it was gonna work, so I rinse repeat 3 times the charm back up an running w/ old dell.
+
+ Additional comment I’ve never changed/tested a c-mos battery another y-tuber mentioned a procedure for replacing the C-MOS which could be a solution for a old dell not booting. Hope this helps someone.
+
+
+
+
+
+ +
+
+ +
 
+ + + +
+ + + + +
+ + + + + + + + + + + + + + + + +
+ + + +
+ + +
+ +
+ +
+
+ + +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ +
+ + +
+
+
+ + S + + +
+
+
+

stevethebrain

+
In Runtime
+ +
+ + + +
+ + + +
+
Messages
+
367
+
+ + + + + + +
+
Location
+
+ + usa + +
+
+ + + + +
+ + + +
+ +
+ + + +
+ +
+ + + + +
+ + + +
+ + + +
+ + + + + + + + + + + + + +
+ + + +
+ + + + +
+ + + +
+ +
+ After watching a bunch of dell PSU failures/swaps I bumped into the organe flashing light a builtin problem w/ dells.
+
+ Also learned about the green light on back of PSU.
+
+
+
+ Weird that the resolution was to remove replace each 4GB RAM module, iniailly didn’t think it was gonna work, so I rinse repeat 3 times the charm back up an running w/ old dell.
+
+ Additional comment I’ve never changed/tested a c-mos battery another y-tuber mentioned a procedure for replacing the C-MOS which could be a solution for a old dell not booting. Hope this helps someone.
+
+
+
+
+
+ +
+
update: next morning dell won't boot up. tryed to remove/replace RAM trick didn't work. the PSU would turn on w/ it's external test button.
+
+ popped in a new watch battery an now she boots up fine. tested the old battery .1vdc tested new battery 3vdc. Hope this helps someone +
+ +
+
+ +
 
+ + + +
+ + + + +
+ + + + + + + + + + + + + + + + +
+ + + +
+ + +
+ +
+ +
+
+ + +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ +
+ + +
+
+
+ + S + + +
+
+
+

stevethebrain

+
In Runtime
+ +
+ + + +
+ + + +
+
Messages
+
367
+
+ + + + + + +
+
Location
+
+ + usa + +
+
+ + + + +
+ + + +
+ +
+ + + +
+ +
+ + + + +
+ + + +
+ + + +
+ + + + + + + + + + + + + +
+ + + +
+ + + + +
update: next morning dell won't boot up again.didn't remove replace RAM.
+
+ I held down the PSUs test button on rear of case kept holding it then pressed power button, 1st attempt she didn't boot but did want to blick the amber power light.
+ 3rd attempt holding both PSUs test button and power button she boots up. so the watch battery didn't fix much.
+
+ anyone here know what seams to be the problem w/ this dell? thanks
+ +
 
+ + + +
+ + + + +
+ + + + + + + + + + + + + + + + +
+ + + +
+ + +
+ +
+ +
+
+ + +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ +
+ + +
+
+
+ + S + + +
+
+
+

stevethebrain

+
In Runtime
+ +
+ + + +
+ + + +
+
Messages
+
367
+
+ + + + + + +
+
Location
+
+ + usa + +
+
+ + + + +
+ + + +
+ +
+ + + +
+ +
+ + + + +
+ + + +
+ + + +
+ + + + + + + + + + + + + +
+ + + +
+ + + + +
tested RAM w/ Windows Memory Diagnostic RAM is fine.
+ still have to press PSU test button an the power button for it to boot anyone know whats happened to the dell?
+ weird that this issue started after SSD install.
+ +
 
+ + + +
+ + + + +
+ + + + + + + + + + + + + + + + +
+ + + +
+ + +
+ +
+ +
+
+ + +
+ + +
+ +
+ +
+ + + + + + + + + + +
+
+ + + + + + + + +
+ + + + + + + + + +
+
+ +

Similar threads

+ +
+
+ + + + + +
+ + +
+
+ + S + + +
+
+ + + +
+ + + + +
+ + + + + + + + + 3 + + 4 + + 5 + + + + +
+
+ + + +
+
+
Replies
+
40
+
+
+
Views
+
2K
+
+
+ + + +
+ + +
+ + markanderson + +
+ +
+ + + +
+
+ + + markanderson + + +
+
+ + +
+ + + + + + +
+ + +
+
+ + S + + +
+
+ + + + + + + +
+
+
Replies
+
0
+
+
+
Views
+
417
+
+
+ + + +
+ + +
+ + stevethebrain + +
+ +
+ + + +
+
+ + + S + + +
+
+ + +
+ + + + + + +
+ + +
+
+ + S + + +
+
+ + + + + + + +
+
+
Replies
+
18
+
+
+
Views
+
1K
+
+
+ + + +
+ + +
+ + LilyRoss + +
+ +
+ + + +
+
+ + + L + + +
+
+ + +
+ + + + + + +
+ + +
+
+ + R + + +
+
+ + + +
+ + + + +
+ + + + + + + + + 2 + + + + +
+
+ + + +
+
+
Replies
+
18
+
+
+
Views
+
2K
+
+
+ + + +
+ + +
+ + PP Mguire + +
+ +
+ + + +
+
+ + + PP Mguire + + +
+
+ + +
+ + + + + + +
+ + +
+
+ + A + + +
+
+ + + + + + + +
+
+
Replies
+
2
+
+
+
Views
+
527
+
+
+ + + +
+ + +
+ + ikonix360 + +
+ +
+ + + +
+
+ + + ikonix360 + + +
+
+ + +
+ + +
+
+ +
+
+ + + + +
+ + + + + + + + +
+ + + + + + + +
+ +
+ + +
+ + + + + + + + +
+
+ + + +
+ +
+ + +
+ + +
+ Top + + Bottom + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +