def getTextWithFields(self, formatConfig: Optional[Dict] = None) -> textInfos.TextInfo.TextWithFieldsT: # Get the initial control fields. fields = [] rootObj = self.obj.rootNVDAObject obj = self._startObj while obj and obj != rootObj: field = self._getControlFieldForObject(obj) if field: fields.insert(0, textInfos.FieldCommand("controlStart", field)) obj = obj.parent embedIndex = None for ti in self._getTextInfos(): for textWithEmbeddedObjectsItem in ti._iterTextWithEmbeddedObjects(True, formatConfig=formatConfig): if isinstance(textWithEmbeddedObjectsItem, int): # Embedded object if embedIndex is None: embedIndex = self._getFirstEmbedIndex(ti) else: embedIndex += 1 childObject: NVDAObject = ti.obj.getChild(embedIndex) controlField = self._getControlFieldForObject(childObject, ignoreEditableText=False) controlField["content"] = childObject.name fields.extend(( textInfos.FieldCommand("controlStart", controlField), textUtils.OBJ_REPLACEMENT_CHAR, textInfos.FieldCommand("controlEnd", None) )) else: # str or fieldCommand if not isinstance(textWithEmbeddedObjectsItem, (str, textInfos.FieldCommand)): log.error(f"Unexpected type: {textWithEmbeddedObjectsItem!r}") fields.append(textWithEmbeddedObjectsItem) return fields
def _startElementHandler(self, tagName, attrs): if tagName == 'unich': data = attrs.get('value', None) if data is not None: try: data = chr(int(data)) except ValueError: data = textUtils.REPLACEMENT_CHAR self._CharacterDataHandler( data, processBufferedSurrogates=isLowSurrogate(data)) return elif tagName == 'control': newAttrs = textInfos.ControlField(attrs) self._commandList.append( textInfos.FieldCommand("controlStart", newAttrs)) elif tagName == 'text': newAttrs = textInfos.FormatField(attrs) self._commandList.append( textInfos.FieldCommand("formatChange", newAttrs)) else: raise ValueError("Unknown tag name: %s" % tagName) # Normalise attributes common to both field types. try: newAttrs["_startOfNode"] = newAttrs["_startOfNode"] == "1" except KeyError: pass try: newAttrs["_endOfNode"] = newAttrs["_endOfNode"] == "1" except KeyError: pass
def _getTextWithFieldsForRange(self, obj, rangeObj, formatConfig): #Graphics usually have no actual text, so render the name instead if rangeObj.CompareEndpoints( UIAHandler.TextPatternRangeEndpoint_Start, rangeObj, UIAHandler .TextPatternRangeEndpoint_End) == 0 and obj != self.obj: for x in obj.makeTextInfo("all").getTextWithFields(formatConfig): yield x return tempRange = rangeObj.clone() children = rangeObj.getChildren() for index in xrange(children.length): child = children.getElement(index) childObj = UIA(UIAElement=child.buildUpdatedCache( UIAHandler.handler.baseCacheRequest)) #Sometimes a child range can contain the same children as its parent (causing an infinite loop) # Example: checkboxes, graphics, in Edge. if childObj == obj: continue childRange = self.obj.UIATextPattern.rangeFromChild(child) if childRange.CompareEndpoints( UIAHandler.TextPatternRangeEndpoint_End, rangeObj, UIAHandler.TextPatternRangeEndpoint_Start ) <= 0 or childRange.CompareEndpoints( UIAHandler.TextPatternRangeEndpoint_Start, rangeObj, UIAHandler.TextPatternRangeEndpoint_End) >= 0: continue if childRange.CompareEndpoints( UIAHandler.TextPatternRangeEndpoint_Start, rangeObj, UIAHandler.TextPatternRangeEndpoint_Start) < 0: childRange.moveEndpointByRange( UIAHandler.TextPatternRangeEndpoint_Start, rangeObj, UIAHandler.TextPatternRangeEndpoint_Start) if childRange.CompareEndpoints( UIAHandler.TextPatternRangeEndpoint_End, rangeObj, UIAHandler.TextPatternRangeEndpoint_End) > 0: childRange.moveEndpointByRange( UIAHandler.TextPatternRangeEndpoint_End, rangeObj, UIAHandler.TextPatternRangeEndpoint_End) tempRange.moveEndpointByRange( UIAHandler.TextPatternRangeEndpoint_End, childRange, UIAHandler.TextPatternRangeEndpoint_Start) for f in self._getFormatFieldsAndText(tempRange, formatConfig): yield f field = self._getControlFieldForObject( childObj) if childObj else None if field: yield textInfos.FieldCommand("controlStart", field) for x in self._getTextWithFieldsForRange(childObj, childRange, formatConfig): yield x if field: yield textInfos.FieldCommand("controlEnd", None) tempRange.moveEndpointByRange( UIAHandler.TextPatternRangeEndpoint_Start, childRange, UIAHandler.TextPatternRangeEndpoint_End) tempRange.moveEndpointByRange(UIAHandler.TextPatternRangeEndpoint_End, rangeObj, UIAHandler.TextPatternRangeEndpoint_End) for f in self._getFormatFieldsAndText(tempRange, formatConfig): yield f
def getTextWithFields(self, formatConfig=None): if not formatConfig: formatConfig = config.conf["documentFormatting"] fields = [] try: e = self._rangeObj.getEnclosingElement().buildUpdatedCache( UIAHandler.handler.baseCacheRequest) except COMError: e = None fields = [] if e: obj = UIA(UIAElement=e) while obj and obj != self.obj: try: field = self._getControlFieldForObject(obj) except LookupError: break if field: field = textInfos.FieldCommand("controlStart", field) fields.append(field) obj = obj.parent fields.reverse() ancestorCount = len(fields) fields.extend( self._getTextWithFieldsForRange(self.obj, self._rangeObj, formatConfig)) fields.extend( textInfos.FieldCommand("controlEnd", None) for x in xrange(ancestorCount)) return fields
def getTextWithFields(self, formatConfig=None): # Get the initial control fields. fields = [] rootObj = self.obj.rootNVDAObject obj = self._startObj while obj and obj != rootObj: field = self._getControlFieldForObject(obj) if field: fields.insert(0, textInfos.FieldCommand("controlStart", field)) obj = obj.parent for ti in self._getTextInfos(): fieldStart = 0 for field in ti.getTextWithFields(formatConfig=formatConfig): if isinstance(field, basestring): textLength = len(field) for chunk in self._iterTextWithEmbeddedObjects( field, ti, fieldStart, textLength=textLength): if isinstance(chunk, basestring): fields.append(chunk) else: controlField = self._getControlFieldForObject( chunk, ignoreEditableText=False) controlField["alwaysReportName"] = True fields.extend( (textInfos.FieldCommand( "controlStart", controlField), u"\uFFFC", textInfos.FieldCommand("controlEnd", None))) fieldStart += textLength else: fields.append(field) return fields
def getTextWithFields(self, formatConfig=None): # Get the initial control fields. fields = [] rootObj = self.obj.rootNVDAObject obj = self._startObj while obj and obj != rootObj: field = self._getControlFieldForObject(obj) if field: fields.insert(0, textInfos.FieldCommand("controlStart", field)) obj = obj.parent embedIndex = None for ti in self._getTextInfos(): for field in ti._iterTextWithEmbeddedObjects(True, formatConfig=formatConfig): if isinstance(field, basestring): fields.append(field) elif isinstance(field, int): # Embedded object if embedIndex is None: embedIndex = self._getFirstEmbedIndex(ti) else: embedIndex += 1 field = ti.obj.getChild(embedIndex) controlField = self._getControlFieldForObject(field, ignoreEditableText=False) controlField["content"] = field.name fields.extend((textInfos.FieldCommand("controlStart", controlField), u"\uFFFC", textInfos.FieldCommand("controlEnd", None))) else: fields.append(field) return fields
def _iterRecursiveText(self, ti: offsets.OffsetsTextInfo, controlStack, formatConfig): if ti.obj == self._endObj: end = True ti.setEndPoint(self._end, "endToEnd") else: end = False for item in ti._iterTextWithEmbeddedObjects(controlStack is not None, formatConfig=formatConfig): if item is None: yield u"" elif isinstance(item, str): yield item elif isinstance(item, int): # Embedded object. embedded: typing.Optional[IAccessible] = _getEmbedded( ti.obj, item) if embedded is None: continue notText = _getRawTextInfo(embedded) is NVDAObjectTextInfo if controlStack is not None: controlField = self._getControlFieldForObject(embedded) controlStack.append(controlField) if controlField: if notText: controlField["content"] = embedded.name controlField["_startOfNode"] = True yield textInfos.FieldCommand("controlStart", controlField) if notText: # A 'stand-in' character is necessary to make routing work on braille devices. # Note #11291: # Using a space character (EG " ") causes 'space' to be announced after objects like graphics. # If this is replaced with an empty string, routing to cell becomes innaccurate. # Using the textUtils.OBJ_REPLACEMENT_CHAR which is the # "OBJECT REPLACEMENT CHARACTER" (EG "\uFFFC") # results in '"0xFFFC' being displayed on the braille device. yield " " else: for subItem in self._iterRecursiveText( self._makeRawTextInfo(embedded, textInfos.POSITION_ALL), controlStack, formatConfig): yield subItem if subItem is None: return if controlStack is not None and controlField: controlField["_endOfNode"] = True del controlStack[-1] yield textInfos.FieldCommand("controlEnd", None) else: yield item if end: # None means the end has been reached and text retrieval should stop. yield None
def _getTextWithFieldsForRange(self, obj, rangeObj, formatConfig): #Graphics usually have no actual text, so render the name instead if obj.role == controlTypes.ROLE_GRAPHIC: yield obj.name return tempRange = rangeObj.clone() children = rangeObj.getChildren() for index in xrange(children.length): child = children.getElement(index) childObj = UIA(UIAElement=child.buildUpdatedCache( UIAHandler.handler.baseCacheRequest)) #Sometimes a child range can contain the same children as its parent (causing an infinite loop) if childObj == obj: continue childRange = self.obj.UIATextPattern.rangeFromChild(child) if childRange.CompareEndpoints( UIAHandler.TextPatternRangeEndpoint_Start, rangeObj, UIAHandler.TextPatternRangeEndpoint_Start) < 0: childRange.moveEndpointByRange( UIAHandler.TextPatternRangeEndpoint_Start, rangeObj, UIAHandler.TextPatternRangeEndpoint_Start) if childRange.CompareEndpoints( UIAHandler.TextPatternRangeEndpoint_End, rangeObj, UIAHandler.TextPatternRangeEndpoint_End) > 0: childRange.moveEndpointByRange( UIAHandler.TextPatternRangeEndpoint_End, rangeObj, UIAHandler.TextPatternRangeEndpoint_End) tempRange.moveEndpointByRange( UIAHandler.TextPatternRangeEndpoint_End, childRange, UIAHandler.TextPatternRangeEndpoint_Start) text = tempRange.getText(-1) if text: yield self._getFormatFieldAtRange(tempRange, formatConfig) yield text field = self._getControlFieldForObject( childObj) if childObj else None if field: yield textInfos.FieldCommand("controlStart", field) for x in self._getTextWithFieldsForRange(childObj, childRange, formatConfig): yield x if field: yield textInfos.FieldCommand("controlEnd", None) tempRange.moveEndpointByRange( UIAHandler.TextPatternRangeEndpoint_Start, childRange, UIAHandler.TextPatternRangeEndpoint_End) tempRange.moveEndpointByRange(UIAHandler.TextPatternRangeEndpoint_End, rangeObj, UIAHandler.TextPatternRangeEndpoint_End) text = tempRange.getText(-1) if text: yield self._getFormatFieldAtRange(tempRange, formatConfig) yield text
def getTextWithFields(self, formatConfig=None): commands = [] if self.isCollapsed: return commands if not formatConfig: formatConfig = config.conf["documentFormatting"] left, top = self._consoleCoordFromOffset(self._startOffset) right, bottom = self._consoleCoordFromOffset(self._endOffset - 1) rect = wincon.SMALL_RECT(left, top, right, bottom) if bottom - top > 0: #offsets span multiple lines rect.Left = 0 rect.Right = self.consoleScreenBufferInfo.dwSize.x - 1 length = self.consoleScreenBufferInfo.dwSize.x * (bottom - top + 1) else: length = self._endOffset - self._startOffset buf = wincon.ReadConsoleOutput(consoleOutputHandle, length, rect) if bottom - top > 0: buf = buf[left:len(buf) - (self.consoleScreenBufferInfo.dwSize.x - right) + 1] lastAttr = None lastText = [] boundEnd = self._startOffset for i, c in enumerate(buf): if self._startOffset + i == boundEnd: field, (boundStart, boundEnd) = self._getFormatFieldAndOffsets( boundEnd, formatConfig) if lastText: commands.append("".join(lastText)) lastText = [] commands.append(textInfos.FieldCommand("formatChange", field)) if not c.Attributes == lastAttr: formatField = textInfos.FormatField() if formatConfig['reportColor']: formatField["color"] = CONSOLE_COLORS_TO_RGB[c.Attributes & 0x0f] formatField["background-color"] = CONSOLE_COLORS_TO_RGB[ (c.Attributes >> 4) & 0x0f] if formatConfig[ 'reportFontAttributes'] and c.Attributes & COMMON_LVB_UNDERSCORE: formatField['underline'] = True if formatField: if lastText: commands.append("".join(lastText)) lastText = [] command = textInfos.FieldCommand("formatChange", formatField) commands.append(command) lastAttr = c.Attributes lastText.append(c.Char) commands.append("".join(lastText)) return commands
def _getFormatFieldAtRange(self, range, formatConfig): formatField = textInfos.FormatField() if formatConfig["reportFontName"]: try: fontNameValue = range.GetAttributeValue( UIAHandler.UIA_FontNameAttributeId) except COMError: fontNameValue = UIAHandler.handler.reservedNotSupportedValue if fontNameValue != UIAHandler.handler.reservedNotSupportedValue: formatField["font-name"] = fontNameValue if formatConfig["reportFontSize"]: try: fontSizeValue = range.GetAttributeValue( UIAHandler.UIA_FontSizeAttributeId) except COMError: fontSizeValue = UIAHandler.handler.reservedNotSupportedValue if fontSizeValue != UIAHandler.handler.reservedNotSupportedValue: formatField['font-size'] = "%g pt" % float(fontSizeValue) if formatConfig["reportHeadings"]: try: styleIDValue = range.GetAttributeValue( UIAHandler.UIA_StyleIdAttributeId) except COMError: styleIDValue = UIAHandler.handler.reservedNotSupportedValue if UIAHandler.StyleId_Heading1 <= styleIDValue <= UIAHandler.StyleId_Heading9: formatField["heading-level"] = ( styleIDValue - UIAHandler.StyleId_Heading1) + 1 return textInfos.FieldCommand("formatChange", formatField)
def _EndElementHandler(self,tagName): if tagName=="control": self._commandList.append(textInfos.FieldCommand("controlEnd",None)) elif tagName in ("text","unich"): pass else: raise ValueError("unknown tag name: %s"%tagName)
def _iterRecursiveText(self, ti, controlStack, formatConfig): if ti.obj == self._endObj: end = True ti.setEndPoint(self._end, "endToEnd") else: end = False for item in ti._iterTextWithEmbeddedObjects(controlStack is not None, formatConfig=formatConfig): if item is None: yield u"" elif isinstance(item, basestring): yield item elif isinstance(item, int): # Embedded object. embedded = _getEmbedded(ti.obj, item) notText = _getRawTextInfo(embedded) is NVDAObjectTextInfo if controlStack is not None: controlField = self._getControlFieldForObject(embedded) controlStack.append(controlField) if controlField: if notText: controlField["content"] = embedded.name controlField["_startOfNode"] = True yield textInfos.FieldCommand("controlStart", controlField) if notText: yield u" " else: for subItem in self._iterRecursiveText( self._makeRawTextInfo(embedded, textInfos.POSITION_ALL), controlStack, formatConfig): yield subItem if subItem is None: return if controlStack is not None and controlField: controlField["_endOfNode"] = True del controlStack[-1] yield textInfos.FieldCommand("controlEnd", None) else: yield item if end: # None means the end has been reached and text retrieval should stop. yield None
def getTextWithFields(self,formatConfig=None): if not formatConfig: formatConfig=config.conf["documentFormatting"] if self.detectFormattingAfterCursorMaybeSlow and not formatConfig['detectFormatAfterCursor']: field,(boundStart,boundEnd)=self._getFormatFieldAndOffsets(self._startOffset,formatConfig,calculateOffsets=False) text=self.text return [textInfos.FieldCommand('formatChange',field),text] commandList=[] offset=self._startOffset while offset<self._endOffset: field,(boundStart,boundEnd)=self._getFormatFieldAndOffsets(offset,formatConfig) if boundEnd<=boundStart: boundEnd=boundStart+1 if boundEnd<=offset: boundEnd=offset+1 command=textInfos.FieldCommand("formatChange",field) commandList.append(command) text=self._getTextRange(offset,min(boundEnd,self._endOffset)) commandList.append(text) offset=boundEnd return commandList
def getTextWithFields(self, formatConfig=None): # Get the initial control fields. fields = [] rootObj = self.obj.rootNVDAObject obj = self._startObj while obj and obj != rootObj: if not ( self._isObjectEditableText(obj) or self._isNamedlinkDestination(obj) ): field = self._getControlFieldForObject(obj) fields.insert(0, textInfos.FieldCommand("controlStart", field)) obj = obj.parent embedIndex = None for ti in self._getTextInfos(): for textWithEmbeddedObjectsItem in ti._iterTextWithEmbeddedObjects(True, formatConfig=formatConfig): if isinstance(textWithEmbeddedObjectsItem, int): # Embedded object if embedIndex is None: embedIndex = self._getFirstEmbedIndex(ti) else: embedIndex += 1 childObject: NVDAObject = ti.obj.getChild(embedIndex) if not ( # Don't check for self._isObjectEditableText # Only for named link destinations. self._isNamedlinkDestination(obj) ): controlField = self._getControlFieldForObject(childObject) controlField["content"] = childObject.name fields.extend(( textInfos.FieldCommand("controlStart", controlField), textUtils.OBJ_REPLACEMENT_CHAR, textInfos.FieldCommand("controlEnd", None), )) else: # str or fieldCommand if not isinstance(textWithEmbeddedObjectsItem, (str, textInfos.FieldCommand)): log.error(f"Unexpected type: {textWithEmbeddedObjectsItem!r}") fields.append(textWithEmbeddedObjectsItem) return fields
def _getFormatFieldsAndText(self, tempRange, formatConfig): if not self.allowGetFormatFieldsAndTextOnDegenerateUIARanges and tempRange.compareEndpoints( UIAHandler.TextPatternRangeEndpoint_Start, tempRange, UIAHandler.TextPatternRangeEndpoint_End) == 0: return formatField = self._getFormatFieldAtRange(tempRange, formatConfig) if formatConfig["reportSpellingErrors"]: try: annotationTypes = tempRange.GetAttributeValue( UIAHandler.UIA_AnnotationTypesAttributeId) except COMError: annotationTypes = UIAHandler.handler.reservedNotSupportedValue if annotationTypes == UIAHandler.AnnotationType_SpellingError: formatField.field["invalid-spelling"] = True yield formatField yield tempRange.GetText(-1) elif annotationTypes == UIAHandler.handler.ReservedMixedAttributeValue: for r in self._iterUIARangeByUnit(tempRange, UIAHandler.TextUnit_Word): text = r.GetText(-1) if not text: continue r.MoveEndpointByRange( UIAHandler.TextPatternRangeEndpoint_End, r, UIAHandler.TextPatternRangeEndpoint_Start) r.ExpandToEnclosingUnit(UIAHandler.TextUnit_Character) try: annotationTypes = r.GetAttributeValue( UIAHandler.UIA_AnnotationTypesAttributeId) except COMError: annotationTypes = UIAHandler.handler.reservedNotSupportedValue newField = textInfos.FormatField() newField.update(formatField.field) if annotationTypes == UIAHandler.AnnotationType_SpellingError: newField["invalid-spelling"] = True yield textInfos.FieldCommand("formatChange", newField) yield text else: yield formatField yield tempRange.GetText(-1) else: yield formatField yield tempRange.GetText(-1)
def _getText(self, withFields, formatConfig=None): fields = [] if self.isCollapsed: return fields if withFields: # Get the initial control fields. controlStack = [] rootObj = self.obj obj = self._startObj ti = self._start cannotBeStart = False while obj and obj != rootObj: field = self._getControlFieldForObject(obj) if field: if ti._startOffset == 0: if not cannotBeStart: field["_startOfNode"] = True else: # We're not at the start of this object, which also means we're not at the start of any ancestors. cannotBeStart = True fields.insert(0, textInfos.FieldCommand("controlStart", field)) controlStack.insert(0, field) ti = self._getEmbedding(obj) obj = ti.obj else: controlStack = None # Get the fields for start. fields += list(self._iterRecursiveText(self._start, controlStack, formatConfig)) if not fields: # We're not getting anything, so the object must be dead. # (We already handled collapsed above.) return fields obj = self._startObj while fields[-1] is not None: # The end hasn't yet been reached, which means it isn't a descendant of obj. # Therefore, continue from where obj was embedded. if withFields: field = controlStack.pop() if field: # This object had a control field. field["_endOfNode"] = True fields.append(textInfos.FieldCommand("controlEnd", None)) ti = self._getEmbedding(obj) obj = ti.obj if ti.move(textInfos.UNIT_OFFSET, 1) == 0: # There's no more text in this object. continue ti.setEndPoint(self._makeRawTextInfo(obj, textInfos.POSITION_ALL), "endToEnd") fields.extend(self._iterRecursiveText(ti, controlStack, formatConfig)) del fields[-1] if withFields: # Determine whether the range covers the end of any ancestors of endObj. obj = self._endObj ti = self._end while obj and obj != rootObj: field = controlStack.pop() if field: fields.append(textInfos.FieldCommand("controlEnd", None)) if ti.compareEndPoints(self._makeRawTextInfo(obj, textInfos.POSITION_ALL), "endToEnd") == 0: field["_endOfNode"] = True else: # We're not at the end of this object, which also means we're not at the end of any ancestors. break ti = self._getEmbedding(obj) obj = ti.obj return fields
def _getTextWithFieldsForUIARange(self,rootElement,textRange,formatConfig,includeRoot=True,recurseChildren=True,alwaysWalkAncestors=True,_rootElementClipped=(True,True)): # Edge zooms into its children at the start. # Thus you are already in the deepest first child. # Therefore get the deepest enclosing element at the start, get its content, Then do the whole thing again on the content from the end of the enclosing element to the end of its parent, and repete! # In other words, get the content while slowly zooming out from the start. log.debug("_getTextWithFieldsForUIARange (unbalanced)") if not recurseChildren: log.debug("recurseChildren is False. Falling back to super") for field in super(EdgeTextInfo,self)._getTextWithFieldsForUIARange(rootElement,textRange,formatConfig,includeRoot=includeRoot,alwaysWalkAncestors=True,recurseChildren=False,_rootElementClipped=_rootElementClipped): yield field return if log.isEnabledFor(log.DEBUG): log.debug("rootElement: %s"%rootElement.currentLocalizedControlType) log.debug("full text: %s"%textRange.getText(-1)) log.debug("includeRoot: %s"%includeRoot) startRange=textRange.clone() startRange.MoveEndpointByRange(UIAHandler.TextPatternRangeEndpoint_End,startRange,UIAHandler.TextPatternRangeEndpoint_Start) enclosingElement=getEnclosingElementWithCacheFromUIATextRange(startRange,self._controlFieldUIACacheRequest) if not enclosingElement: log.debug("No enclosingElement. Returning") return enclosingRange=self.obj.getNormalizedUIATextRangeFromElement(enclosingElement) if not enclosingRange: log.debug("enclosingRange is NULL. Returning") return if log.isEnabledFor(log.DEBUG): log.debug("enclosingElement: %s"%enclosingElement.currentLocalizedControlType) startRange.MoveEndpointByRange(UIAHandler.TextPatternRangeEndpoint_End,enclosingRange,UIAHandler.TextPatternRangeEndpoint_End) if startRange.CompareEndpoints(UIAHandler.TextPatternRangeEndpoint_End,textRange,UIAHandler.TextPatternRangeEndpoint_End)>0: startRange.MoveEndpointByRange(UIAHandler.TextPatternRangeEndpoint_End,textRange,UIAHandler.TextPatternRangeEndpoint_End) # Ensure we don't now have a collapsed range if startRange.CompareEndpoints(UIAHandler.TextPatternRangeEndpoint_End,startRange,UIAHandler.TextPatternRangeEndpoint_Start)<=0: log.debug("Collapsed range. Returning") return # check for an embedded child childElements=getChildrenWithCacheFromUIATextRange(startRange,self._controlFieldUIACacheRequest) if childElements.length==1 and UIAHandler.handler.clientObject.compareElements(rootElement,childElements.getElement(0)): log.debug("Using single embedded child as enclosingElement") for field in super(EdgeTextInfo,self)._getTextWithFieldsForUIARange(rootElement,startRange,formatConfig,_rootElementClipped=_rootElementClipped,includeRoot=includeRoot,alwaysWalkAncestors=False,recurseChildren=False): yield field return parents=[] parentElement=enclosingElement log.debug("Generating ancestors:") hasAncestors=False while parentElement: if log.isEnabledFor(log.DEBUG): log.debug("parentElement: %s"%parentElement.currentLocalizedControlType) isRoot=UIAHandler.handler.clientObject.compareElements(parentElement,rootElement) log.debug("isRoot: %s"%isRoot) if not isRoot: hasAncestors=True if parentElement is not enclosingElement: if includeRoot or not isRoot: try: obj=UIA(windowHandle=self.obj.windowHandle,UIAElement=parentElement,initialUIACachedPropertyIDs=self._controlFieldUIACachedPropertyIDs) field=self._getControlFieldForObject(obj) except LookupError: log.debug("Failed to fetch controlField data for parentElement. Breaking") break parents.append((parentElement,field)) else: # This is the root but it was not requested for inclusion # However we still need the root element itself for further recursion parents.append((parentElement,None)) if isRoot: log.debug("Hit root. Breaking") break log.debug("Fetching next parentElement") parentElement=UIAHandler.handler.baseTreeWalker.getParentElementBuildCache(parentElement,self._controlFieldUIACacheRequest) log.debug("Done generating parents") log.debug("Yielding parents in reverse order") for parentElement,field in reversed(parents): if field: yield textInfos.FieldCommand("controlStart",field) log.debug("Done yielding parents") log.debug("Yielding balanced fields for startRange") clippedStart=enclosingRange.CompareEndpoints(UIAHandler.TextPatternRangeEndpoint_Start,startRange,UIAHandler.TextPatternRangeEndpoint_Start)<0 clippedEnd=enclosingRange.CompareEndpoints(UIAHandler.TextPatternRangeEndpoint_End,startRange,UIAHandler.TextPatternRangeEndpoint_End)>0 for field in super(EdgeTextInfo,self)._getTextWithFieldsForUIARange(enclosingElement,startRange,formatConfig,_rootElementClipped=(clippedStart,clippedEnd),includeRoot=includeRoot or hasAncestors,alwaysWalkAncestors=False,recurseChildren=True): yield field tempRange=startRange.clone() log.debug("Walking parents to yield controlEnds and recurse unbalanced endRanges") for parentElement,field in parents: if log.isEnabledFor(log.DEBUG): log.debug("parentElement: %s"%parentElement.currentLocalizedControlType) tempRange.MoveEndpointByRange(UIAHandler.TextPatternRangeEndpoint_Start,tempRange,UIAHandler.TextPatternRangeEndpoint_End) parentRange=self.obj.getNormalizedUIATextRangeFromElement(parentElement) if parentRange: tempRange.MoveEndpointByRange(UIAHandler.TextPatternRangeEndpoint_End,parentRange,UIAHandler.TextPatternRangeEndpoint_End) if tempRange.CompareEndpoints(UIAHandler.TextPatternRangeEndpoint_End,textRange,UIAHandler.TextPatternRangeEndpoint_End)>0: tempRange.MoveEndpointByRange(UIAHandler.TextPatternRangeEndpoint_End,textRange,UIAHandler.TextPatternRangeEndpoint_End) clippedEnd=True else: clippedEnd=False if field: clippedStart=parentRange.CompareEndpoints(UIAHandler.TextPatternRangeEndpoint_Start,textRange,UIAHandler.TextPatternRangeEndpoint_Start)<0 field['_startOfNode']=not clippedStart field['_endOfNode']=not clippedEnd if tempRange.CompareEndpoints(UIAHandler.TextPatternRangeEndpoint_End,tempRange,UIAHandler.TextPatternRangeEndpoint_Start)>0: log.debug("Recursing endRange") for endField in self._getTextWithFieldsForUIARange(parentElement,tempRange,formatConfig,_rootElementClipped=(clippedStart,clippedEnd),includeRoot=False,alwaysWalkAncestors=True,recurseChildren=True): yield endField log.debug("Done recursing endRange") else: log.debug("No content after parent") if field: log.debug("Yielding controlEnd for parent") yield textInfos.FieldCommand("controlEnd",field) log.debug("Done walking parents to yield controlEnds and recurse unbalanced endRanges") log.debug("_getTextWithFieldsForUIARange (unbalanced) end")
def getTextWithFields(self, formatConfig=None): if self.isCollapsed: # #7652: We cannot fetch fields on collapsed ranges otherwise we end up with repeating controlFields in braille (such as list list list). return [] fields = super(WordDocumentTextInfo, self).getTextWithFields(formatConfig=formatConfig) if len(fields) == 0: # Nothing to do... was probably a collapsed range. return fields # Sometimes embedded objects and graphics In MS Word can cause a controlStart then a controlEnd with no actual formatChange / text in the middle. # SpeakTextInfo always expects that the first lot of controlStarts will always contain some text. # Therefore ensure that the first lot of controlStarts does contain some text by inserting a blank formatChange and empty string in this case. for index in range(len(fields)): field = fields[index] if isinstance(field, textInfos.FieldCommand ) and field.command == "controlStart": continue elif isinstance( field, textInfos.FieldCommand) and field.command == "controlEnd": formatChange = textInfos.FieldCommand("formatChange", textInfos.FormatField()) fields.insert(index, formatChange) fields.insert(index + 1, "") break ##7971: Microsoft Word exposes list bullets as part of the actual text. # This then confuses NVDA's braille cursor routing as it expects that there is a one-to-one mapping between characters in the text string and unit character moves. # Therefore, detect when at the start of a list, and strip the bullet from the text string, placing it in the text's formatField as line-prefix. listItemStarted = False lastFormatField = None for index in range(len(fields)): field = fields[index] if isinstance(field, textInfos.FieldCommand ) and field.command == "controlStart": if field.field.get( 'role' ) == controlTypes.ROLE_LISTITEM and field.field.get( '_startOfNode'): # We are in the start of a list item. listItemStarted = True elif isinstance(field, textInfos.FieldCommand ) and field.command == "formatChange": # This is the most recent formatField we have seen. lastFormatField = field.field elif listItemStarted and isinstance(field, str): # This is the first text string within the list. # Remove the text up to the first space, and store it as line-prefix which NVDA will appropriately speak/braille as a bullet. try: spaceIndex = field.index(' ') except ValueError: log.debugWarning("No space found in this text string") break prefix = field[0:spaceIndex] fields[index] = field[spaceIndex + 1:] lastFormatField['line-prefix'] = prefix # Let speech know that line-prefix is safe to be spoken always, as it will only be exposed on the very first formatField on the list item. lastFormatField['line-prefix_speakAlways'] = True break else: # Not a controlStart, formatChange or text string. Nothing to do. break # Fill in page number attributes where NVDA expects try: page = fields[0].field['page-number'] except KeyError: page = None if page is not None: for field in fields: if isinstance(field, textInfos.FieldCommand) and isinstance( field.field, textInfos.FormatField): field.field['page-number'] = page # MS Word can sometimes return a higher ancestor in its textRange's children. # E.g. a table inside a table header. # This does not cause a loop, but does cause information to be doubled # Detect these duplicates and remove them from the generated fields. seenStarts = set() pendingRemoves = [] index = 0 for index, field in enumerate(fields): if isinstance(field, textInfos.FieldCommand ) and field.command == "controlStart": runtimeID = field.field['runtimeID'] if not runtimeID: continue if runtimeID in seenStarts: pendingRemoves.append(field.field) else: seenStarts.add(runtimeID) elif seenStarts: seenStarts.clear() index = 0 while index < len(fields): field = fields[index] if isinstance(field, textInfos.FieldCommand) and any( x is field.field for x in pendingRemoves): del fields[index] else: index += 1 return fields
def getTextWithFields( # noqa: C901 self, formatConfig: Optional[Dict] = None ) -> textInfos.TextInfo.TextWithFieldsT: fields = None # #11043: when a non-collapsed text range is positioned within a blank table cell # MS Word does not return the table cell as an enclosing element, # Thus NVDa thinks the range is not inside the cell. # This can be detected by asking for the first 2 characters of the range's text, # Which will either be an empty string, or the single end-of-row mark. # Anything else means it is not on an empty table cell, # or the range really does span more than the cell itself. # If this situation is detected, # copy and collapse the range, and fetch the content from that instead, # As a collapsed range on an empty cell does correctly return the table cell as its first enclosing element. if not self.isCollapsed: rawText = self._rangeObj.GetText(2) if not rawText or rawText == END_OF_ROW_MARK: r = self.copy() r.end = r.start fields = super(WordDocumentTextInfo, r).getTextWithFields(formatConfig=formatConfig) if fields is None: fields = super().getTextWithFields(formatConfig=formatConfig) if len(fields) == 0: # Nothing to do... was probably a collapsed range. return fields # MS Word tries to produce speakable math content within equations. # However, using mathPlayer with the exposed mathml property on the equation is much nicer. # But, we therefore need to remove the inner math content if reading by line if not formatConfig or not formatConfig.get('extraDetail'): # We really only want to remove content if we can guarantee that mathPlayer is available. mathPres.ensureInit() if mathPres.speechProvider or mathPres.brailleProvider: curLevel = 0 mathLevel = None mathStartIndex = None mathEndIndex = None for index in range(len(fields)): field = fields[index] if isinstance(field, textInfos.FieldCommand ) and field.command == "controlStart": curLevel += 1 if mathLevel is None and field.field.get('mathml'): mathLevel = curLevel mathStartIndex = index elif isinstance(field, textInfos.FieldCommand ) and field.command == "controlEnd": if curLevel == mathLevel: mathEndIndex = index curLevel -= 1 if mathEndIndex is not None: del fields[mathStartIndex + 1:mathEndIndex] # Sometimes embedded objects and graphics In MS Word can cause a controlStart then a controlEnd with no actual formatChange / text in the middle. # SpeakTextInfo always expects that the first lot of controlStarts will always contain some text. # Therefore ensure that the first lot of controlStarts does contain some text by inserting a blank formatChange and empty string in this case. for index in range(len(fields)): field = fields[index] if isinstance(field, textInfos.FieldCommand ) and field.command == "controlStart": continue elif isinstance( field, textInfos.FieldCommand) and field.command == "controlEnd": formatChange = textInfos.FieldCommand("formatChange", textInfos.FormatField()) fields.insert(index, formatChange) fields.insert(index + 1, "") break ##7971: Microsoft Word exposes list bullets as part of the actual text. # This then confuses NVDA's braille cursor routing as it expects that there is a one-to-one mapping between characters in the text string and unit character moves. # Therefore, detect when at the start of a list, and strip the bullet from the text string, placing it in the text's formatField as line-prefix. listItemStarted = False lastFormatField = None for index in range(len(fields)): field = fields[index] if isinstance(field, textInfos.FieldCommand ) and field.command == "controlStart": if field.field.get( 'role' ) == controlTypes.Role.LISTITEM and field.field.get( '_startOfNode'): # We are in the start of a list item. listItemStarted = True elif isinstance(field, textInfos.FieldCommand ) and field.command == "formatChange": # This is the most recent formatField we have seen. lastFormatField = field.field elif listItemStarted and isinstance(field, str): # This is the first text string within the list. # Remove the text up to the first space, and store it as line-prefix which NVDA will appropriately speak/braille as a bullet. try: spaceIndex = field.index(' ') except ValueError: log.debugWarning("No space found in this text string") break prefix = field[0:spaceIndex] fields[index] = field[spaceIndex + 1:] lastFormatField['line-prefix'] = prefix # Let speech know that line-prefix is safe to be spoken always, as it will only be exposed on the very first formatField on the list item. lastFormatField['line-prefix_speakAlways'] = True break else: # Not a controlStart, formatChange or text string. Nothing to do. break # Fill in page number attributes where NVDA expects try: page = fields[0].field['page-number'] except KeyError: page = None if page is not None: for field in fields: if isinstance(field, textInfos.FieldCommand) and isinstance( field.field, textInfos.FormatField): field.field['page-number'] = page # MS Word can sometimes return a higher ancestor in its textRange's children. # E.g. a table inside a table header. # This does not cause a loop, but does cause information to be doubled # Detect these duplicates and remove them from the generated fields. seenStarts = set() pendingRemoves = [] index = 0 for index, field in enumerate(fields): if isinstance(field, textInfos.FieldCommand ) and field.command == "controlStart": runtimeID = field.field['runtimeID'] if not runtimeID: continue if runtimeID in seenStarts: pendingRemoves.append(field.field) else: seenStarts.add(runtimeID) elif seenStarts: seenStarts.clear() index = 0 while index < len(fields): field = fields[index] if isinstance(field, textInfos.FieldCommand) and any( x is field.field for x in pendingRemoves): del fields[index] else: index += 1 return fields
def _getText(self, withFields, formatConfig=None): fields = [] if self.isCollapsed: return fields if withFields: # Get the initial control fields. controlStack = [] rootObj = self.obj obj = self._startObj ti = self._start cannotBeStart = False while obj and obj != rootObj: field = self._getControlFieldForObject(obj) if field: if ti._startOffset == 0: if not cannotBeStart: field["_startOfNode"] = True else: # We're not at the start of this object, which also means we're not at the start of any ancestors. cannotBeStart = True fields.insert(0, textInfos.FieldCommand("controlStart", field)) controlStack.insert(0, field) ti = self._getEmbedding(obj) if not ti: log.debugWarning( "_getEmbedding returned None while getting initial fields. " "Object probably dead." ) return [] obj = ti.obj else: controlStack = None # Get the fields for start. fields += list(self._iterRecursiveText(self._start, controlStack, formatConfig)) if not fields: # We're not getting anything, so the object must be dead. # (We already handled collapsed above.) return fields obj = self._startObj while fields[-1] is not None: # The end hasn't yet been reached, which means it isn't a descendant of obj. # Therefore, continue from where obj was embedded. if withFields: try: field = controlStack.pop() except IndexError: # We're trying to walk up past our root. This can happen if a descendant # object within the range died, in which case _iterRecursiveText will # never reach our end object and thus won't yield None. This means this # range is invalid, so just return nothing. log.debugWarning("Tried to walk up past the root. Objects probably dead.") return [] if field: # This object had a control field. field["_endOfNode"] = True fields.append(textInfos.FieldCommand("controlEnd", None)) ti = self._getEmbedding(obj) if not ti: log.debugWarning( "_getEmbedding returned None while ascending to get more text. " "Object probably dead." ) return [] obj = ti.obj if ti.move(textInfos.UNIT_OFFSET, 1) == 0: # There's no more text in this object. continue ti.setEndPoint(self._makeRawTextInfo(obj, textInfos.POSITION_ALL), "endToEnd") fields.extend(self._iterRecursiveText(ti, controlStack, formatConfig)) del fields[-1] if withFields: # Determine whether the range covers the end of any ancestors of endObj. obj = self._endObj ti = self._end while obj and obj != rootObj: field = controlStack.pop() if field: fields.append(textInfos.FieldCommand("controlEnd", None)) if ti.compareEndPoints(self._makeRawTextInfo(obj, textInfos.POSITION_ALL), "endToEnd") == 0: if field: field["_endOfNode"] = True else: # We're not at the end of this object, which also means we're not at the end of any ancestors. break ti = self._getEmbedding(obj) obj = ti.obj return fields